#
# syntax.g
# Potion tokens and grammar
#
# (c) 2009 _why
#

%{
#include <stdio.h>
#include <stdlib.h>
#include "potion.h"
#include "internal.h"
#include "asm.h"
#include "ast.h"

#define YY_INPUT(buf, result, max, P) { \
  if (P->yypos < PN_STR_LEN(P->input)) { \
    result = max; \
    if (P->yypos + max > PN_STR_LEN(P->input)) \
      result = (PN_STR_LEN(P->input) - P->yypos); \
    PN_MEMCPY_N(buf, PN_STR_PTR(P->input) + P->yypos, char, result + 1); \
    P->yypos += max; \
  } else { \
    result = 0; \
  } \
}

#define YYSTYPE PN
#define YY_XTYPE Potion *
#define YY_XVAR P
#define YY_NAME(N) potion_code_##N

#define YY_TNUM 3
#define YY_TDEC 13

%}

potion = -- s:statements end-of-file { $$ = P->source = PN_AST1(AST_CODE, s); }

statements = s1:stmt { $$ = s1 = PN_TUP(s1); }
        (sep s2:stmt { $$ = s1 = PN_PUSH(s1, s2); })*
         sep?
     | ''            { $$ = PN_NIL; }

# TODO hsq 复合赋值操作符把接收者（首参）加算了 2 次。
# TODO hsq 小括号不能用来改变优先级？如 a = a || b 中 = 优先级高于 or ， a = (a || b) 的结果是元组。
# TODO hsq 收集支持的但不在文档中的操作符；操作符的优先级： || or && and 《
#   = ||=_or= &&=_and= |= ^= &= <<= >>= += -= *= /= %= **= 《
#   <=> == != 《 >= > <= < 《 | ^ 《 & 《 << >> 《 + - 《 * / % ** 《 + - ~ ! not 《 ++ --
  stmt = s:sets
        ( or x:sets          { s = PN_AST2(AST_OR, s, x); }
        | and x:sets         { s = PN_AST2(AST_AND, s, x); })*
        { $$ = s; }

  sets = e:eqs
        ( assign s:sets       { e = PN_AST2(AST_ASSIGN, e, s); }
        | or assign s:sets    { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_OR, e, s)); }
        | and assign s:sets   { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_AND, e, s)); }
        | pipe assign s:sets  { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_PIPE, e, s)); }
        | caret assign s:sets { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_CARET, e, s)); }
        | amp assign s:sets   { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_AMP, e, s)); }
        | bitl assign s:sets  { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_BITL, e, s)); }
        | bitr assign s:sets  { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_BITR, e, s)); }
        | plus assign s:sets  { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_PLUS, e, s)); }
        | minus assign s:sets { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_MINUS, e, s)); }
        | times assign s:sets { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_TIMES, e, s)); }
        | div assign s:sets   { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_DIV, e, s)); }
        | rem assign s:sets   { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_REM, e, s)); }
        | pow assign s:sets   { e = PN_AST2(AST_ASSIGN, e, PN_AST2(AST_POW, e, s)); })?
        { $$ = e; }

  eqs = c:cmps
        ( cmp x:cmps          { c = PN_AST2(AST_CMP, c, x); }
        | eq x:cmps           { c = PN_AST2(AST_EQ, c, x); }
        | neq x:cmps          { c = PN_AST2(AST_NEQ, c, x); })*
        { $$ = c; }

  cmps = o:bitors
        ( gte x:bitors        { o = PN_AST2(AST_GTE, o, x); }
        | gt x:bitors         { o = PN_AST2(AST_GT, o, x); }
        | lte x:bitors        { o = PN_AST2(AST_LTE, o, x); }
        | lt x:bitors         { o = PN_AST2(AST_LT, o, x); })*
        { $$ = o; }

  bitors = a:bitand
          ( pipe x:bitand       { a = PN_AST2(AST_PIPE, a, x); }
          | caret x:bitand      { a = PN_AST2(AST_CARET, a, x); })*
          { $$ = a; }

  bitand = b:bitshift
          ( amp x:bitshift      { b = PN_AST2(AST_AMP, b, x); })*
          { $$ = b; }

  bitshift = s:sum
            ( bitl x:sum          { s = PN_AST2(AST_BITL, s, x); }
            | bitr x:sum          { s = PN_AST2(AST_BITR, s, x); })*
            { $$ = s; }

  sum = p:product
        ( plus x:product      { p = PN_AST2(AST_PLUS, p, x); }
        | minus x:product     { p = PN_AST2(AST_MINUS, p, x); })*
        { $$ = p; }

  product = s:sign
            ( times x:sign           { s = PN_AST2(AST_TIMES, s, x); }
            | div x:sign             { s = PN_AST2(AST_DIV, s, x); }
            | rem x:sign             { s = PN_AST2(AST_REM, s, x); }
            | pow x:sign             { s = PN_AST2(AST_POW, s, x); })*
            { $$ = s; }

  sign = minus !minus s:sign   { $$ = PN_AST2(AST_MINUS, PN_AST1(AST_VALUE, PN_ZERO), s); }
      | plus !plus s:sign     { $$ = PN_AST2(AST_PLUS, PN_AST1(AST_VALUE, PN_ZERO), s); }
      | not s:sign     { $$ = PN_AST1(AST_NOT, s); }
      | wavy s:sign    { $$ = PN_AST1(AST_WAVY, s); }
      | e:expr         { $$ = e; }

  # 末位决定是否后缀（否则前缀），作为 number 的值决定符号。
  expr = ( mminus a:atom { a = PN_AST2(AST_INC, a, PN_NUM(-1) ^ PN_FNUMBER); }
        | pplus a:atom   { a = PN_AST2(AST_INC, a, PN_NUM(1) ^ PN_FNUMBER); }
        | a:atom (pplus  { a = PN_AST2(AST_INC, a, PN_NUM(1)); }
                | mminus { a = PN_AST2(AST_INC, a, PN_NUM(-1)); })?) { a = PN_TUP(a); }
          (c:call { a = PN_PUSH(a, c) })*
        { $$ = PN_AST1(AST_EXPR, a); }
        # TODO hsq AST_EXPR 除发消息外，都只有一个参数？单参时消除 AST_EXPR ？统计。
        # TODO hsq 需要处理消除的元组的 GC ？
        #{ $$ = PN_TUPLE_LEN(a) == 1 ? PN_TUPLE_AT(a, 0) : PN_AST1(AST_EXPR, a); }
# TODO hsq ++ 和 -- 可用于 atom 中的非数字类型？甚至非左值。结果都是 nil 。

atom = e:value | e:closure | e:table | e:call

call = (n:name { v = PN_NIL; b = PN_NIL; } (v:value | v:table)? |
       (v:value | v:table) { n = PN_AST1(AST_MESSAGE, PN_NIL); b = PN_NIL; })
         b:block? { $$ = n; PN_AST_ARG(n, 1) = v; PN_AST_ARG(n, 2) = b; }

# TODO hsq 1?/ 语法错误， 1?➗ 不会。
name = p:path           { $$ = PN_AST1(AST_PATH, p); }
     | quiz ( m:message { $$ = PN_AST1(AST_QUERY, m); }
            | p:path    { $$ = PN_AST1(AST_PATHQ, p); })
     | !keyword
       m:message        { $$ = PN_AST1(AST_MESSAGE, m); }

# lick 子结构
  lick-items = i1:lick-item     { $$ = i1 = PN_TUP(i1); }
              (sep i2:lick-item { $$ = i1 = PN_PUSH(i1, i2); })*
              sep?
            | ''               { $$ = PN_NIL; }

  lick-item = m:message t:table v:loose { $$ = PN_AST3(AST_LICK, m, v, t); }
            | m:message t:table { $$ = PN_AST3(AST_LICK, m, PN_NIL, t); }
            | m:message v:loose t:table { $$ = PN_AST3(AST_LICK, m, v, t); }
            | m:message v:loose { $$ = PN_AST2(AST_LICK, m, v); }
            | m:message         { $$ = PN_AST1(AST_LICK, m); }

  loose = value
        | v:unquoted { $$ = PN_AST1(AST_VALUE, v); }

closure = t:table? b:block { $$ = PN_AST2(AST_PROTO, t, b); }
# TODO hsq 表和改变表达式的优先级的括号，如何区分？参考 PN_ARG() 。
table = table-start s:statements table-end { $$ = PN_AST1(AST_TABLE, s); }
block = block-start s:statements block-end { $$ = PN_AST1(AST_BLOCK, s); }
lick = lick-start i:lick-items lick-end { $$ = PN_AST1(AST_TABLE, i); }

path = '/' message      { $$ = potion_str2(P, yytext, yyleng); }
message = < utfw+ > -   { $$ = potion_str2(P, yytext, yyleng); }

value = i:immed - { $$ = PN_AST1(AST_VALUE, i); }
      | lick

# TODO hsq 十六进制数字没有限制长度；十进制 10 位以下用整数，最大 0x3B9AC9FF ，超过用浮点数表示。
# 立即值： nil bool number string
immed = nil   { $$ = PN_NIL; }
      | true  { $$ = PN_TRUE; }
      | false { $$ = PN_FALSE; }
      | hex   { $$ = PN_NUM(PN_ATOI(yytext, yyleng, 16)); }
      | dec   { if ($$ == YY_TNUM && yyleng < 10) {
                  $$ = PN_NUM(PN_ATOI(yytext, yyleng, 10));
                } else {
                  $$ = potion_decimal(P, yytext, yyleng);
              } }
      | str1 | str2

# 命名
# TODO hsq 添加问号解决 nil nil? 语法错误问题，观察是否带来其他问题。
#   message = < utfw+ '?'? > 也能解决，但是 nil? 在表达式首仍然报错，因为在 atom 中， nil = "nil" !utfw 规则在前，
#   这就是 ? 也作为操作符引入的歧义。或者强制要求 token 之间必需有空白、分隔符，因此连着的就是一个 token ？
utfw = [A-Za-z0-9_$@;`{}?]
     | '\304' [\250-\277]
     | [\305-\337] [\200-\277]
     | [\340-\357] [\200-\277] [\200-\277]
     | [\360-\364] [\200-\277] [\200-\277] [\200-\277]
utf8 = [\t\n\r\40-\176]
     | [\302-\337] [\200-\277]
     | [\340-\357] [\200-\277] [\200-\277]
     | [\360-\364] [\200-\277] [\200-\277] [\200-\277]

# 操作符
  comma = ','
  block-start = ':' --
  block-end = '.' -
  table-start = '(' --
  table-end = ')' -
  lick-start = '[' --
  lick-end = ']' -
  quiz = '?' --
  assign = '=' --
  pplus = "++" -
  mminus = "--" -
  minus = '-' --
  plus = '+' --
  wavy = '~' --
  times = '*' --
  div = '/' --
  rem = '%' --
  pow = "**" --
  bitl = "<<" --
  bitr = ">>" --
  amp = '&' --
  caret = '^' --
  pipe = '|' --
  lt = '<' --
  lte = "<=" --
  gt = '>' --
  gte = ">=" --
  neq = "!=" --
  eq = "==" --
  cmp = "<=>" --
  and = ("&&" | "and" !utfw) --
  or = ("||" | "or" !utfw) --
  not = ("!" | "not" !utfw) --
  keyword = "and" | "or" | "not"

# nil bool number
  nil = "nil" !utfw
  true = "true" !utfw
  false = "false" !utfw
  hexl = [0-9A-Fa-f]
  hex = '0x' < hexl+ >
  dec = < ('0' | [1-9][0-9]*) { $$ = YY_TNUM; }
          ('.' [0-9]+ { $$ = YY_TDEC; })?
          ('e' [-+] [0-9]+ { $$ = YY_TDEC })? >

# 单引号字符串
  q1 = [']
  c1 = < (!q1 utf8)+ > { P->pbuf = potion_asm_write(P, P->pbuf, yytext, yyleng); }
  str1 = q1 { P->pbuf = potion_asm_clear(P, P->pbuf); }
        < (q1 q1 { P->pbuf = potion_asm_write(P, P->pbuf, "'", 1); } | c1)* >
        q1 { $$ = PN_STR_B((PN)P->pbuf); }

# 双引号字符串
  esc         = '\\'
  escn        = esc 'n' { P->pbuf = potion_asm_write(P, P->pbuf, "\n", 1); }
  escb        = esc 'b' { P->pbuf = potion_asm_write(P, P->pbuf, "\b", 1); }
  escf        = esc 'f' { P->pbuf = potion_asm_write(P, P->pbuf, "\f", 1); }
  escr        = esc 'r' { P->pbuf = potion_asm_write(P, P->pbuf, "\r", 1); }
  esct        = esc 't' { P->pbuf = potion_asm_write(P, P->pbuf, "\t", 1); }
  escu        = esc 'u' < hexl hexl hexl hexl > {
    int nbuf = 0;
    char utfc[4] = {0, 0, 0, 0};
    unsigned long code = PN_ATOI(yytext, yyleng, 16);
    if (code < 0x80) {
      utfc[nbuf++] = code;
    } else if (code < 0x7ff) {
      utfc[nbuf++] = (code >> 6) | 0xc0;
      utfc[nbuf++] = (code & 0x3f) | 0x80;
    } else {
      utfc[nbuf++] = (code >> 12) | 0xe0;
      utfc[nbuf++] = ((code >> 6) & 0x3f) | 0x80;
      utfc[nbuf++] = (code & 0x3f) | 0x80;
    }
    P->pbuf = potion_asm_write(P, P->pbuf, utfc, nbuf);
  }
  escc = esc < utf8 > { P->pbuf = potion_asm_write(P, P->pbuf, yytext, yyleng); }

  q2 = ["]
  e2 = '\\' ["] { P->pbuf = potion_asm_write(P, P->pbuf, "\"", 1); }
  c2 = < (!q2 !esc utf8)+ > { P->pbuf = potion_asm_write(P, P->pbuf, yytext, yyleng); }
  str2 = q2 { P->pbuf = potion_asm_clear(P, P->pbuf); }
        < (e2 | escn | escb | escf | escr | esct | escu | escc | c2)* >
        q2 { $$ = PN_STR_B((PN)P->pbuf); }

# 括号成对出现的非引用 utf8 序列
  unq-char = '{' unq-char+ '}'
          | '[' unq-char+ ']'
          | '(' unq-char+ ')'
          | !'{' !'[' !'(' !'}' !']' !')' utf8
  unq-sep = sep !'{' !'[' !'('
  unquoted = < (!unq-sep !lick-end unq-char)+ > { $$ = potion_str2(P, yytext, yyleng); }
  # TODO hsq !lick-end 没必要，去掉可通过测试； !unq-sep 去掉则测试报错，何用？

# 空白等
  - = (space | comment-block | comment )*
  -- = (space | comment-block | comment | end-of-line)*
  sep = (end-of-line | comma) (space | comment-block | comment | end-of-line | comma)*
  # TODO hsq 单行注释一般后跟 EOL 或 EOF 。
  comment	= '#' (!end-of-line utf8)*
  # 注释 #{{ ... }} ，非注释 #{{ ... #}}
  # comment-block = '#{{' (!'#}}' !'}}' utf8)+ '}}'
  # 注释 #{{ ... #}} ，非注释 ##{{ ... #}}
  comment-block = !'##' '#{{' (!'}}' utf8)+ '}}'
  space = ' ' | '\f' | '\v' | '\t'
  end-of-line = '\r\n' | '\n' | '\r'
  end-of-file = !.

# 函数参数签名
# TODO hsq 注意：这里仅仅作用于注册消息时指定的参数签名，而非函数定义本身的参数列表，因此符号 | 和 . 不可用于函数定义。
# TODO hsq greg 基于较早版本的 peg ，后者不支持混合多个 grammar ，因此这里符号 $ 导致 leg 报错，
#   且需要拆分成多个文件，以及其他大量代码修改，因为接口有很大差异。
#   leg 支持在生成的 c 文件中标注对应 g 文件的行号，这需要支持匹配期间的 action ， greg 不支持，因此语法错无法报告出错的文本行。
  sig = args+ end-of-file
  args = arg-list (arg-sep arg-list)*
  arg-list = arg-set (optional arg-set)?
          | optional arg-set
  arg-set = arg (comma - arg)*

  arg-name = < utfw+ > - { $$ = potion_str2(P, yytext, yyleng); }
  arg-type = < ('s' | 'S' | 'n' | 'N' | 'b' | 'B' | 'k' | 't' | 'o' | 'O' | '-' | '&') > -
        { $$ = PN_NUM(yytext[0]); }
  arg = n:arg-name assign t:arg-type
                        { P->source = PN_PUSH(PN_PUSH(PN_PUSH(P->source, n), t), PN_NIL); }
      | t:arg-type       { P->source = PN_PUSH(P->source, t); }
  # TODO hsq 注释掉 optional arg-sep 的处理，保留语法；也影响 potion_sig_find 和 potion_proto_string 。
  optional = '|' -       #{ P->source = PN_PUSH(P->source, PN_NUM('|')); }
  arg-sep = '.' -        #{ P->source = PN_PUSH(P->source, PN_NUM('.')); }

%%

// 解析源码 PNBytes code -> PNSource | error 。
PN potion_parse(Potion *P, PN code) {
  PN err = PN_NIL;
  GREG *G = potion_code_parse_new(P);
  P->yypos = 0;
  P->input = code;
  P->source = PN_NIL;
  P->pbuf = potion_asm_new(P);

  G->pos = G->limit = 0;
  int ok = potion_code_parse(G);
  if (!ok) {
    // printf("\e[1;31m** Syntax error!\e[0m\n");
    char buf[64];
    int pos = G->begin; // end
    int len = snprintf(buf, sizeof(buf) - 1, "%s", G->buf + pos);
    buf[len] = '\0';
    // printf("\e[1;31m**\n%d:%s \n**\e[0m\n", pos, buf);
    PN msg = PN_LITERAL("\e[1;31mSyntax error!\e[0m");
    PN excerpt = potion_str2(P, buf, len);
    err = potion_error(P, msg, 0, pos, excerpt);
    // TODO hsq 如何加入行号？搜索 potion_error 函数。
    // TODO hsq 报语法错误时， valgrind 会发现 uninitialised value 。
  }
  potion_code_parse_free(G);

  code = P->source;
  P->source = PN_NIL;
  // if (!ok && !P->in_repl) exit(EXIT_FAILURE);
  return err != PN_NIL ? err : code;
}

// 解析字符串 fmt 生成参数签名元组；
//    NULL 无签名返回 PN_NIL ，空串空签名无参数则为 PN_FALSE 。
// 无名参数仅压入类型字符的编码，命名参数压入名字、类型字符编码和 nil ，
//    可选符号、参数列表分隔符也压入其字符编码。
PN potion_sig(Potion *P, char *fmt) {
  PN out = PN_NIL;
  if (fmt == NULL) return PN_NIL; // no signature, arg check off
  if (fmt[0] == '\0') return PN_FALSE; // empty signature, no args

  GREG *G = potion_code_parse_new(P);
  P->yypos = 0;
  P->input = potion_bytes_str(P, fmt);
  P->source = out = PN_TUP0();
  P->pbuf = NULL;

  G->pos = G->limit = 0;
  if (!potion_code_parse_from(G, yy_sig))
    printf("\e[1;31m** Syntax error (sig)!\e[0m\n");
  potion_code_parse_free(G);

  out = P->source;
  P->source = PN_NIL;
  return out;
}

// 在闭包 cl 的签名元组 .sig 中查找命名参数 name ，返回参数编号，无则为 -1 。
//    cl 不是元组则取其 .call 闭包 。
int potion_sig_find(Potion *P, PN cl, PN name)
{
  PN_SIZE idx = 0; // 元组中的参数编号
  PN sig;
  if (!PN_IS_CLOSURE(cl))
    cl = potion_obj_get_call(P, cl);

  if (!PN_IS_CLOSURE(cl))
    return -1;

  sig = PN_CLOSURE(cl)->sig;

  if (!PN_IS_TUPLE(sig))
    return -1;

  PN_TUPLE_EACH(sig, i, v, {
    if (v == name)
      return idx;
    if (PN_IS_NUM(v) /* && (v != PN_NUM('.') && v != PN_NUM('|')) */)
      idx++;
  });

  return -1;
}
